package logic;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;

import network.Client;
import network.InformClients;
import network.Server;

import org.eclipse.swt.graphics.Color;

import gamemngr.GameMngr;
import gamemngr.Rules;
import logic.moves.GameMove;
import logic.strategy.RandomStrategy;
import logic.strategy.Strategy;
	
public class Player implements Serializable {
	
	//=== Fields ===
	private static final long serialVersionUID = 1L;
	private String 						playerName;
	private HashMap<String, Country>	countries;
	private Stack<GameMove> moves;
	private Stack<GameMove> redoMoves;
	private int soldiers = 21; // the number of initial soldiers
	private int totalSoldiers = this.soldiers;
	private int numberOfMoves;
	private int totalNumOfMoves = 0;
	private Rules rules;
	private Mission secretMission;
	private boolean	computerPlayer;
	private Strategy strategy;
	private transient Color colour;
	private int level;
	private GameMngr game;
	private boolean me;
	
	public Player(String playerName, GameMngr game) {
		this.playerName = playerName;
		countries = new HashMap<String, Country>();
		moves = new Stack<GameMove>();
		redoMoves = new Stack<GameMove>();
		rules = new Rules(game);
		computerPlayer = false;
		this.game = game;
	}
	
	public Player(String playerName, Color colour, GameMngr game) {
		this(playerName, game);
		this.colour = colour;
		this.game = game;
	}

	/**
	 * @return the colour
	 */
	public Color getColour() {
		return colour;
	}

	/**
	 * @param colour the colour to set
	 */
	public void setColour(Color colour) {
		this.colour = colour;
	}

	/**
	 * return true if this player is the owner of counter
	 */
	public boolean hasCountry(Country country){
		return countries.containsKey(country.toString());
	}
	
	/**
	 * setting the player to own the country
	 */
	public void addCountry(Country country){
		countries.put(country.toString(), country);
		country.setOwner(this);
	}
	
	/**
	 * the input is the country's name
	 * removes the country from this player ownership
	 * setting the owner of the country to be null
	 */
	public void removeCountry(String countryName)	{
		removeCountry(countries.get(countryName));
	}
	
	/**
	 * the input is the country
	 * removes the country from this player ownership
	 * setting the owner of the country to be null
	 */
	public void removeCountry(Country country){
		countries.remove(country.getName());
		country.setOwner(null);
	}

	/**
	 * @return this player's name
	 */
	public String getPlayerName(){
		return playerName;
	}

	/**
	 * @param move - add move to player
	 * when the player made a move, all of the moves history is deleted
	 * @param isNetwork TODO
	 */
	public boolean addMove(GameMove move, boolean isNetwork) {
		if(isNetwork && !me){
			return false;
		}
		if (rules.isLegal(move)){
			if(move.makeMove()){
				move.printDoMove();
				moves.add(move);
				this.numberOfMoves++;
				this.redoMoves.clear();
			}
		}
		
		if(isNetwork){
			Client.sendMsg(new InformClients(this.game, move.getBriefDo()));
		}
		
		return true;
	}
	
	/**
	 * Is the player reached the number of moves limitation
	 * ONLY IF HOUSE RULES WHERE CHANGED
	 * @return - true if the player reached the limitation, otherwise false.
	 */
	public boolean CheckMovesRule() {
		return  ((game.getNumMoveRule()!= -1)  && (numberOfMoves == game.getNumMoveRule()));
	}
	
	/**
	 * undoing the players last move 
	 */
	public boolean undoMove(boolean isNetwork){
		if(isNetwork && !me){
			return false;
		}
		if (!moves.isEmpty()){
			GameMove gameMove = moves.peek();
			if(gameMove.getType().equalsIgnoreCase("ATTACKMOVE")) {
				return false;
			}
			gameMove.printUndoMove();
			gameMove.restore();
			redoMoves.push(moves.pop());
			this.numberOfMoves--;

			if(isNetwork){
				Client.sendMsg(new InformClients(this.game, redoMoves.peek().getBriefUndo()));
			}
			
			return true;
		}
		return false;
	}
	
	/**
	 * redoing the players last move
	 */
	public boolean redoMove(boolean isNetwork){
		if(isNetwork && !me){
			return false;
		}
		if (!redoMoves.isEmpty()){
			GameMove gameMove = redoMoves.pop();
			gameMove.printDoMove();
			gameMove.makeMove();
			moves.push(gameMove);
			this.numberOfMoves++;
			
			if(isNetwork){
				Client.sendMsg(new InformClients(this.game, moves.peek().getBriefDo()));
			}
			
			return true;
		}
		else
			return false;
	}
	
	/**
	 * making all the moves the player made and than 
	 * clearing the players-moves history
	 */
	public boolean commitMoves(){
		if(this.moves.size() == 0){
			return true;
		}
		// make all the done moves and prompt for the user
		System.out.println();
		System.out.println();
		System.out.println("----SUMMARY----");
		for(GameMove gameMove: moves)
			gameMove.printShortDescription();
		System.out.println();
		moves.clear();
		redoMoves.clear();
		this.totalNumOfMoves += this.numberOfMoves;
		this.numberOfMoves = 0;
		return true;
	}
	
	/**
	 * @return the moves
	 */
	public Stack<GameMove> getMoves() {
		return moves;
	}

	/**
	 * @return the countries
	 */
	public HashMap<String, Country> getCountries() {
		return countries;
	}
	
	/**
	 * save the player's data in the file
	 * @param bWriter - the stream to save into
	 */
	public void savePlayer(BufferedWriter bWriter){
		String color = null;
		if(this.colour == null){
			color = "null";
		} else {
			color = this.colour.getRed() + ";" + this.colour.getGreen() + ";" + this.colour.getBlue();
		}
		try {
			bWriter.write(this.playerName + ":" + this.soldiers + ":" + this.totalSoldiers +":" + this.numberOfMoves + ":" + color + "\n");
		} catch (IOException e) {
			System.err.println("An error occured while saving, please try again later.");		}
	}
	
	/**
	 * place num soldiers on the country
	 */
	public void placeSoldiers(Country country, int num){
		country.addSoldiers(num);
		this.soldiers -= num;
	}
	
	public void placeWithoutDecSoldiers(Country country, int num){
		country.addSoldiers(num);
		this.totalSoldiers += num;
	}
	
	/**
	 * DO NOT USE THIS FUNCTION
	 * this decreased by one the amount place soldiers
	 * and do not decrease the amount on the country in which he was  placed
	 */
	public void killSoldier(){
		this.totalSoldiers--;
	}
	
	/**
	 * returns true iff this player is still in the game
	 * @return
	 */
	public boolean isAlive(){
		return (this.totalSoldiers > 0);
	}
	
	/**
	 * returns the number of moves this player has done
	 */
	public int getNumberOfMoves(){
		return this.numberOfMoves;
	}
	
	public int getTotalNumOfMoves() {
		return (this.totalNumOfMoves + this.numberOfMoves);
	}
	
	public boolean isDefeated(){
		return countries.isEmpty();
	}
	
	/**
	 * @param mission, setter for player's mission
	 * @MUST initStrategy for computer
	 */
	public void setSecretMission(Mission mission) {
		this.secretMission = mission;
	}
	
	public Mission getSecretMission() {
		return this.secretMission;
	}
	
	/**
	 * Read the name of the function
	 */
	public void unplaceSoldier(Country country){
		this.unplaceSoldiers(country, 1);
	}
	
	public void unplaceSoldiers(Country country, int amount){
		this.soldiers += amount;
		for(int i=0; i<amount; i++){
			country.killSoldier();
		}
	}
	
	public int getSoldiers(){
		return this.soldiers;
	}
	
	public void setFreeSolidersAmount(int amount){
		this.soldiers = amount;
	}
	
	public void setTotalSoldiersAmount(int amount){
		this.totalSoldiers = amount;
	}

	public void setNumberOfMoves(int numberOfMoves) {
		this.numberOfMoves = numberOfMoves;
	}
	
	public boolean equals(Object object){
		return this.playerName.equals(((Player)object).getPlayerName());
	}

	public void markAsComputer() {
		computerPlayer = true;
	}
	
	public void setLevel(int level){
		this.level = level;
	}
	
	public int getLevel(){
		return level;
	}
	
	/**
	 * 
	 * @param map
	 * Used after secret mission is set.
	 * @MUST initStrategy for computer Player
	 * @USE only after setSecretMission
	 */
	public void initStrategy(GameMap map) {
		if(getLevel() == 1) // Beginner
			strategy = new RandomStrategy();
		if(getLevel() == 2) // Expert
			strategy = secretMission.getStrategy();
		strategy.init(map, this);		
	}

	/**
	 * 
	 * @return player is computer
	 */
	public boolean isComputer() {
		return computerPlayer;
	}

	/**
	 * 
	 * @return true iff computer player has another move to make
	 * Use before getNextMove()
	 * if false, may passTurn to next player
	 */
	public boolean hasNextMove() {
		return strategy.hasNextMove();
	}

	/**
	 * 
	 * @return next game move, if hasNextMove() == true
	 * otherwise null
	 */
	public GameMove getNextMove() {
		return strategy.getNextMove();
	}
	
	public boolean isMissionAccomplished()
	{
		return secretMission.isMissionAccomplished(this);
	}
	
	/**
	 * 
	 * @param i
	 * @return the i-th (zero-based) Country of the player
	 * @MUSTNOT Be used as iterator. terribly inefficient
	 */
	public Country getCountryByIndex(int i)
	{
		if(i > -1 && i < getCountries().size())
		{
			Iterator<Country> iter = getCountries().values().iterator(); 
			for(int j = 0; j < i; j++)
			{
				iter.next();
			}
			return iter.next();
		}
		else
			return null;
	}

	public boolean hasMoves() {
		return moves.isEmpty() == false;
	}
	
	public boolean hasRedoMoves() {
		return redoMoves.isEmpty() == false;
	}
	
	public String toString()
	{
		return playerName;
	}
	
	public boolean canUndoLastMove()
	{
		if(hasMoves())
		{
			if(moves.peek().getType().equalsIgnoreCase("ATTACKMOVE"))
				return false;
			return true;
		}
		return false;
	}

	/**
	 * @param game the game to set
	 */
	public void setGame(GameMngr game) {
		this.game = game;
	}

	/**
	 * @return the game
	 */
	public GameMngr getGame() {
		return game;
	}

	/**
	 * @param me the me to set
	 */
	public void setMe(boolean me) {
		this.me = me;
	}

	/**
	 * @return the me
	 */
	public boolean isMe() {
		return me;
	}

	/**
	 * @return the redoMoves
	 */
	public Stack<GameMove> getRedoMoves() {
		return redoMoves;
	}
	
	
	

}
